package com.example.gmallproduct.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.example.gmallproduct.VO.web.Catalog2Vo;

import com.example.gmallproduct.VO.web.Catelog3Vo;
import com.example.gmallproduct.service.CategoryBrandRelationService;
import com.example.gmallproduct.utils.RandomNum;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.common.utils.PageUtils;
import com.common.utils.Query;

import com.example.gmallproduct.dao.CategoryDao;
import com.example.gmallproduct.entity.CategoryEntity;
import com.example.gmallproduct.service.CategoryService;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Autowired
    private CategoryBrandRelationService brandRelationService;
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    /**
     * 用于组装树状分类列表
     *
     * @return
     */
    @Override
    public List<CategoryEntity> listToTree() {
        //baseMapper来源于继承的serviceImpl
        //所有分类
        List<CategoryEntity> all = baseMapper.selectList(null);
        //查找顶级列表
        List<CategoryEntity> list = all.stream().filter(categoryEntity -> categoryEntity.getParentCid() == 0)
                .map(categoryEntity -> {
                    categoryEntity.setChildren(getChildren(categoryEntity, all));
                    return categoryEntity;
                })
                .sorted((menu1, menu2) -> {
                    return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
                }).collect(Collectors.toList());

        return list;
    }

    @Override
    public void removeByLogic(List<Long> asList) {

        baseMapper.deleteBatchIds(asList);
    }

    @Override
    public void updateCaseCade(CategoryEntity category) {
        brandRelationService.updateCategory(category.getCatId(), category.getName());
    }

    @Override
    public List<CategoryEntity> listAllRoot() {
        QueryWrapper<CategoryEntity> wrapper = new QueryWrapper<>();
        wrapper.lambda().eq(CategoryEntity::getParentCid, 0);
        return baseMapper.selectList(wrapper);
    }

    /**
     * 使用redission实现分布式锁
     */
//    public Map<String, List<Catalog2Vo>> getCatalogByRedissionLock() {
//        ValueOperations<String, String> ops = redisTemplate.opsForValue();
//        String catalogJson = ops.get("catalogJson");
//        if (catalogJson == null) {
//            RLock lock = redissonClient.getLock("catalog_lock");
//            lock.lock();
//            Map<String, List<Catalog2Vo>> map;
//            try {
//                map = getCatalogJsonByDb();
//                System.out.println("查询了数据库");
//                ops.set("catalogJson", JSON.toJSONString(map));
//            } finally {
//                 lock.unlock();
//            }
//        }
//        return JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {});
//    }

    /**
     * 使用redis作为分布式锁,解决多服务并发查询问题
     *
     * @return
     */
    public Map<String, List<Catalog2Vo>> getCatalogJsonByRedisLock() {
        ValueOperations<String, String> ops = redisTemplate.opsForValue();
        String catalogJson = ops.get("catalogJson");
        if (catalogJson == null) {
            String uuid = UUID.randomUUID().toString();
            Boolean lock = ops.setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
            if (lock) {
                Map<String, List<Catalog2Vo>> map;
                try {
                    map = getCatalogJsonByDb();
                    System.out.println("查询了数据库");
                    ops.set("catalogJson", JSON.toJSONString(map));
                } finally {
                    //通过lua脚本实现删除操作
                    String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                    redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);
                }
                return map;
            } else {
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                getCatalogJsonByRedisLock();
            }
        }
        return JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {
        });
    }


    @Override
    public Map<String, List<Catalog2Vo>> getCatalogJson() {
        return getCatalogJsonByLocalLock();
    }

    /**
     * 多次查询数据库版本
     *
     * @return
     */
    public Map<String, List<Catalog2Vo>> getCatalogJsonByDb1() {
        return listAllRoot().stream().collect(Collectors.toMap(l1 -> l1.getCatId().toString(), l1 -> {
            //2. 创建所有的二级菜单
            QueryWrapper<CategoryEntity> wrapper = new QueryWrapper<>();
            wrapper.lambda().eq(CategoryEntity::getParentCid, l1.getCatId());
            List<CategoryEntity> l2List = baseMapper.selectList(wrapper);
            List<Catalog2Vo> l2VoList = null;
            if (l2List != null) {
//                List<Long> l2Ids = l2List.stream().map(l2 -> l2.getCatId()).collect(Collectors.toList());
                // 2.1 查出当前一级id所对应的所有二级菜单,封装成Map<String,List<Catelog2Vo>>>
                l2VoList = l2List.stream().map(l2 -> {
                    //2.2 查出当前二级id所对应的三级菜单
                    QueryWrapper<CategoryEntity> wrapper1 = new QueryWrapper<>();
                    wrapper1.lambda().eq(CategoryEntity::getParentCid, l2.getCatId());
                    List<CategoryEntity> l3List = baseMapper.selectList(wrapper);
                    List<Catelog3Vo> l3VoList = null;
                    if (l3List != null) {
                        l3VoList = l3List.stream().map(l3 -> {
                            return new Catelog3Vo("" + l2.getCatId(), l3.getCatId().toString(), l3.getName());
                        }).collect(Collectors.toList());
                    }
                    return new Catalog2Vo(l1.getCatId().toString(), l3VoList, l2.getCatId().toString(), l2.getName());
                }).collect(Collectors.toList());
            }
            return l2VoList;
        }));
    }


    /**
     * 使用本地锁查询分类数据
     *
     * @return
     */

    public Map<String, List<Catalog2Vo>> getCatalogJsonByLocalLock() {
        /**
         *
         *  2. 解决缓存击穿问题(重复访问缓存中没有的值)--存储空值
         *  3. 解决缓存雪崩问题(大面积key同时失效)--过期时间差异化
         *  4. 解决缓存穿透问题(热点key失效)---加锁
         */
        ValueOperations<String, String> ops = redisTemplate.opsForValue();
        String catalogJson = ops.get("catalogJson");
        if (catalogJson == null) {
            synchronized (this) {
                //这里的非空判断是由于所有线程都被卡在上一个判断之后,一个线程查询出结果之后其他进入判断的线程直接返回即可
                if (catalogJson == null) {
                    //1. 缓存中没有就去查数据库
                    Map<String, List<Catalog2Vo>> jsonByDb = getCatalogJsonByDb();
                    System.out.println("查询了数据库.....");
                    catalogJson = JSON.toJSONString(jsonByDb);
                    //解决缓存击穿,缓存雪崩问题
                    ops.set("catalogJson", catalogJson == null ? "0" : catalogJson, RandomNum.randomExpireTime(1000), TimeUnit.SECONDS);
                }
            }
        }
        return JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {
        });
    }

    /**
     * 对原生多次查询数据库代码的逻辑优化版本
     *
     * @return
     */
    public Map<String, List<Catalog2Vo>> getCatalogJsonByDb() {
        final List<CategoryEntity> allCategories = baseMapper.selectList(null);
        return getCatalogByParentId(allCategories, 0L).stream().collect(Collectors.toMap(l1 -> l1.getCatId().toString(), l1 -> {
            //2. 创建所有的二级菜单
            List<CategoryEntity> l2List = getCatalogByParentId(allCategories, l1.getCatId());
            List<Catalog2Vo> l2VoList = null;
            if (l2List != null) {
//                List<Long> l2Ids = l2List.stream().map(l2 -> l2.getCatId()).collect(Collectors.toList());
                // 2.1 查出当前一级id所对应的所有二级菜单,封装成Map<String,List<Catelog2Vo>>>
                l2VoList = l2List.stream().map(l2 -> {
                    //2.2 查出当前二级id所对应的三级菜单
                    List<CategoryEntity> l3List = getCatalogByParentId(allCategories, l2.getCatId());
                    List<Catelog3Vo> l3VoList = null;
                    if (l3List != null) {
                        l3VoList = l3List.stream().map(l3 -> {
                            return new Catelog3Vo("" + l2.getCatId(), l3.getCatId().toString(), l3.getName());
                        }).collect(Collectors.toList());
                    }
                    return new Catalog2Vo(l1.getCatId().toString(), l3VoList, l2.getCatId().toString(), l2.getName());
                }).collect(Collectors.toList());
            }
            return l2VoList;
        }));
    }

    private List<CategoryEntity> getCatalogByParentId(List<CategoryEntity> allCategories, Long parentId) {
        return allCategories.stream().filter(categoryEntity -> categoryEntity.getParentCid() == parentId).collect(Collectors.toList());
    }

    /**
     * 从all列表中查找root元素的子列表
     *
     * @param root 当前分类对象
     * @param all  所有类别对象
     * @return root的子分类对象
     */
    private List<CategoryEntity> getChildren(CategoryEntity root, List<CategoryEntity> all) {
        List<CategoryEntity> children = all.stream()
                .filter(cat -> cat.getParentCid().equals(root.getCatId()))
                .map(cat -> {
                    cat.setChildren(getChildren(cat, all));
                    return cat;
                })
                .sorted((cat1, cat2) -> {
                    return (cat1.getSort() == null ? 0 : cat1.getSort()) - (cat2.getSort() == null ? 0 : cat2.getSort());
                })
                .collect(Collectors.toList());
        return children;
    }


}